import queue
import threading
import pandas as pd
import time
import traceback  # 添加在文件顶部
from DrissionPage import Chromium, ChromiumOptions, SessionOptions
from DrissionPage.common import Settings
from email_permission import email_list
from DataRecorder import Recorder

from read_search_file import get_complete_sheet1, get_set_note_ids
from create_unit import create_unit
from loguru import logger
from datetime import datetime
import os
def log_message(log_callback, message, color=None):
    if callable(log_callback):
        log_callback(message, color) if color else log_callback(message)


lock = threading.Lock()


def is_number(value):
    return type(value) in (int, float, complex)
def parse_time_input(time_input):
    time_input = time_input.strip()
    if '-' in time_input:
        # 处理时间范围
        try:
            start, end = map(int, time_input.split('-'))
            if not (0 <= start <= 23 and 0 <= end <= 23):
                raise ValueError("时间范围必须在 0-23 之间")
            if start <= end:
                return list(range(start, end + 1))
        except ValueError:
            raise ValueError(f"无效的时间范围格式: {time_input}")
    else:
        # 单个时间字符串
        try:
            hour = int(time_input)
            if 0 <= hour <= 23:
                return [hour]
            else:
                raise ValueError("时间必须在 0-23 之间")
        except ValueError:
            raise ValueError(f"无效的时间格式: {time_input}")
def chinese_week_day_to_number(day_str):
    day_str = day_str.strip()  # 去除前后空格
    days = {
        "周一": 1,
        "周二": 2,
        "周三": 3,
        "周四": 4,
        "周五": 5,
        "周六": 6,
        "周日": 7
    }
    return days.get(day_str)

# tag:div@@class=flexible@@text()=点击成本控制

def create_plan(plan_view_tab, row_excel, file_path, log_callback, df_sheet, browser):
    # plan_view_tab.set.scroll.smooth(on_off=False)

    plan_view_tab.wait.load_start()
    plan = row_excel['计划名']
    unit_name = row_excel['单元名称']
    market_appeal = row_excel['营销诉求']
    launch_loc = row_excel['投放位置']
    opti_target = row_excel['优化目标']
    trans_platform = row_excel['转化平台']
    deep_target_enable = row_excel['深度优化目标启用']
    deep_target = row_excel['深度优化目标']
    bidding_strategy = row_excel['竞价策略']
    plan_budget = row_excel['指定预算']

    plan_view_tab.wait.eles_loaded('tag:h5@@text()= 计划 ')

    ## 输入计划名称
    plan_view_tab.ele('tag:h5@@text()= 计划 ').next().ele('tag:input@@class=d-text@@maxlength=50').input(plan[:49], clear=True)

    # 营销诉求
    plan_view_tab.ele(f'tag:span@@class^d-text --color-static --color-primary --size-text-paragraph@@text()={market_appeal}').parent(1).click(by_js=True)
    if plan_view_tab.ele('tag:span@@text()=修改后，已填写的广告内容将会被清空', timeout=0.5):
        plan_view_tab.ele('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()=修改').parent(2).click(by_js=True)

    time.sleep(0.5)

    # 投放位置
    plan_view_tab.ele(f'tag:span@@class=form-item-label-text@@text()=投放位置').scroll.to_center()
    plan_view_tab.ele(f'tag:span@@class=form-item-label-text@@text()=投放位置').parent(2).ele(f'tag:span@@text()={launch_loc}').click(by_js=True)
    time.sleep(0.5)

    # 投放模式 - 搜索推广
    if '投放模式' in df_sheet.columns:
        launch_mode = row_excel['投放模式']

        plan_view_tab.ele(f'tag:span@@class=form-item-label-text@@text()=投放模式').scroll.to_center()
        if launch_mode_ele := plan_view_tab.ele(f'tag:span@@class=form-item-label-text@@text()=投放模式').parent(2).ele(f'tag:span@@text()={launch_mode}'):
            launch_mode_ele.click(by_js=True)

    # 推广SPU
    if '推广SPU' in df_sheet.columns:
        promote_spu = row_excel['推广SPU']
        recom_spu_func(plan_view_tab, promote_spu, log_callback)

    # 点击优化目标
    if opti_target_ele := plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()={opti_target}', timeout=2):
        opti_target_ele.scroll.to_center()
        opti_target_ele.parent(3).click(by_js=True)
        time.sleep(0.3)

    # 深度优化目标启用
    if deep_target_enable_ele := plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()={deep_target_enable}', timeout=1):
        deep_target_enable_ele.scroll.to_center()
        deep_target_enable_ele.parent(2).click(by_js=True)
        time.sleep(0.3)

    # 深度优化目标
    if plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()={deep_target}', timeout=1):
        plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()={deep_target}').parent(4).run_js('this.click();')
        time.sleep(0.3)

    # 转化平台
    if plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()={trans_platform}', timeout=1):
        plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()={trans_platform}').parent(4).run_js('this.click();')

    # 投放时段
    if '投放时段' in df_sheet.columns:
        ad_period = row_excel['投放时段']
        launch_time_period(ad_period, plan_view_tab)

    # 竞价策略
    fund_bidding_strategy(bidding_strategy, plan_view_tab)

    # 计划预算
    plan_budget_func(plan_budget, plan_view_tab, df_sheet, row_excel)

    # 搜索快投
    func_sear_fast(plan_view_tab, df_sheet)

    # 广告组配置
    ad_group_conf(df_sheet, plan_view_tab, row_excel)

    create_unit(plan_view_tab, row_excel, browser, file_path, df_sheet, log_callback)


def recom_spu_func(plan_view_tab, promote_spu, log_callback):
    if pd.notna(promote_spu):
        promote_spu = promote_spu.strip()
        if promote_spu == '不关联':
            plan_view_tab.ele('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()=不关联').parent(2).click(by_js=True)
        else:
            if relative_ele := plan_view_tab.ele('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()=关联', timeout=2):
                relative_ele.parent(2).click(by_js=True)
                time.sleep(1)
                if promote_spu_option_ele := plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()^{promote_spu}', timeout=1.5):
                    promote_spu_option_ele.scroll.to_center()
                    promote_spu_option_ele.run_js('this.click();')
                else:
                    log_message(log_callback, f"当前SPU:{promote_spu}在可选列表中不存在, 选择不关联继续执行", "red")
                    plan_view_tab.ele('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()=不关联').parent(2).click(by_js=True)

def ad_group_conf(df_sheet, plan_view_tab, row_excel):
    if '广告组配置' not in df_sheet.columns:
        plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=不加入').parent(3).click(by_js=True)
    else:
        ad_group_name = row_excel['广告组配置']

        if pd.isna(ad_group_name):
            plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=不加入').parent(3).click(by_js=True)
        else:
            ad_group_name = ad_group_name.strip()
            plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=加入').parent(3).click(by_js=True)
            time.sleep(0.5)
            if ad_group_name_ele := plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text()={ad_group_name}', timeout=1):
                ad_group_name_ele.parent(4).run_js('this.click();')
            elif plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text():{ad_group_name}', timeout=1):
                plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text():{ad_group_name}').parent(4).run_js('this.click();')
            else:
                plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=不加入').parent(1).click(by_js=True)


def func_sear_fast(plan_view_tab, df_sheet):
    if '搜索快投' in df_sheet.columns:
        if search_ele := plan_view_tab.ele('tag:span@@class=form-item-label-text@@text()=搜索快投', timeout=1):
            search_ele.scroll.to_center()
            sear_class_str = search_ele.parent(1).next().ele('tag:div@@class=d-switch-top').child().attr('class')
            if 'unchecked' not in sear_class_str:
                search_ele.parent(1).next().child().child().run_js('this.click();')


def plan_budget_func(plan_budget, plan_view_tab, df_sheet, row_excel):
    if pd.notna(plan_view_tab):  # 指定预算
        if is_number(plan_budget):  ## 指定预算
            if plan_budget_ele := plan_view_tab.ele(f'tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()=指定预算', timeout=2):
                plan_budget_ele.scroll.to_center()
                plan_budget_ele.parent(2).click(by_js=True)
                time.sleep(0.5)
                plan_budget_ele.parent(3).next().ele('tag:input@@class=d-text@@type=text@@placeholder=请输入').input(str(plan_budget), clear=True)

                # 取消节假日付算上浮
                if check_ele := plan_view_tab.ele('tag:span@@class=form-item-label-text@@text()=节假日预算上浮', timeout=1.5):
                    holiday_class_str = check_ele.parent(1).next().ele('.=d-switch-top').child().attr('class')
                    if 'unchecked' not in holiday_class_str:
                        check_ele.parent(1).next().child().child().click(by_js=True)

                # 点击均匀消耗
                if '消耗速率' in df_sheet.columns:
                    consume_rate = row_excel['消耗速率']
                    if consume_rate == '匀速消耗' or consume_rate == '均匀消耗':
                        if plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=匀速消耗', timeout=1.5):
                            plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=匀速消耗').scroll.to_center()
                            plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=匀速消耗').parent(3).click(by_js=True)
                    else:
                        if plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=加速消耗', timeout=1.5):
                            plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=加速消耗').scroll.to_center()
                            plan_view_tab.ele(f'tag:div@@class=flexible align-center@@text()=加速消耗').parent(3).click(by_js=True)

        else:  ## 不限预算
            if plan_budget_ele := plan_view_tab.ele(f'tag:span@@class=form-item-label-text@@text()=计划预算', timeout=1.5):
                plan_budget_ele.parent(1).next().ele('tag:button@@text()=不限').click(by_js=True)


def fund_bidding_strategy(bidding_strategy, plan_view_tab):
    if pd.notna(bidding_strategy):
        bidding_strategy = bidding_strategy.strip()
        if bidding_strategy_ele := plan_view_tab.ele(f'tag:span@@class^d-text --color-static --color-primary --size-text-paragraph@@text()={bidding_strategy}', timeout=1):
            bidding_strategy_ele.run_js('this.click();')


def launch_time_period(ad_period, plan_view_tab):
    if pd.notna(ad_period):
        ad_period = ad_period.strip()
        if ad_period != '不限':
            if target_time_ele := plan_view_tab.ele('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()=指定时段', timeout=1):
                target_time_ele.scroll.to_center()
                target_time_ele.parent(2).click(by_js=True)
                time.sleep(0.5)

            if time_table_ele := plan_view_tab.ele('tag:div@@class=week-time-picker', timeout=2):
                if clear_ele := plan_view_tab.ele('tag:a@@class=d-text d-link --color-primary --size-text-paragraph@@text()= 清空 ', timeout=1):
                    clear_ele.click(by_js=True)
                    time.sleep(0.5)

                ad_period_list = ad_period.split('\n')
                for ad_period_item in ad_period_list:
                    ad_period_item = ad_period_item.strip()
                    if '：' in ad_period_item:
                        ad_period_item = ad_period_item.replace('：', ':')
                    ad_period_item.split(':')
                    if len(ad_period_item.split(':')) == 2:
                        week_time, hour_time = ad_period_item.split(':')
                        if week_time and hour_time:
                            week_time_num = chinese_week_day_to_number(week_time)
                            week_ele = time_table_ele.ele('.=drag-content ui-container').ele(locator='tag:div@@class=row-grid', index=week_time_num)
                            hour_list = parse_time_input(hour_time)
                            for hour in hour_list:
                                week_ele.ele(f'tag:div@@class=time-slice@@data-col-index={hour}').run_js('this.click();')
            if add_period_ele := plan_view_tab('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis d-text-nowrap@@text()=添加时段', timeout=1):
                add_period_ele.parent(2).run_js('this.click();')
                time.sleep(0.5)



'''点击新建计划'''
def insert_plan(row_excel, create_plan_tab, file_path, log_callback, df_sheet, browser):
    plan_ele = create_plan_tab.ele('tag:button@@text():新建计划')

    ignore_pop_ele = create_plan_tab.ele('@text()=暂时忽略', timeout=0.5)
    if ignore_pop_ele:
        ignore_pop_ele.click(by_js=True)
    try:
        lock.acquire()
        plan_view_tab = plan_ele.click.for_new_tab(by_js=True)  # 点击新建计划按钮并打开创建计划表单页面
        plan_view_tab.wait.title_change("新建计划 - 小红书聚光")
    finally:
        lock.release()

    create_plan(plan_view_tab, row_excel, file_path, log_callback, df_sheet, browser)

# def handle_all_excel_data(df_sheet_info, file_path, log_callback=None):
#     for index, row in df_sheet_info.iterrows():
#         try:
#             log_message(log_callback, f"处理中: {row['计划名']}")
#             insert_plan(row, file_path)  # 调用函数并传入当前行数据
#         except Exception as e:
#             r = Recorder(f'error-{datetime.now().strftime("%Y-%m-%d")}.csv')
#             r.add_data([row['账号名称'], row['计划名'], datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(e)])  # 添加数据
#             r.record()  # 记录数据
#             log_message(log_callback, f"执行错误: {row['计划名']}", "red") # 前端日志
#             logger.error(f"错误时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - 出错的行数据计划名:{row['计划名']} - 错误日志: {str(e)}")
#             continue
#
#     log_message(log_callback, f"所有计划执行完毕", "green")


def worker(task_queue, create_plan_tab, file_path, log_callback, df_sheet, browser):
    """
    工作线程函数，从队列中获取任务并执行自动化操作
    """
    while True:
        row_excel = task_queue.get()
        if row_excel is None:  # 结束信号
            break
        try:
            log_message(log_callback, f"处理中: {row_excel['计划名']}")
            insert_plan(row_excel, create_plan_tab, file_path, log_callback, df_sheet, browser)
        except Exception as e:
            # 获取完整错误信息
            error_msg = f"{type(e).__name__}: {str(e)}"
            error_trace = traceback.format_exc()  # 获取完整堆栈跟踪

            # 前端显示简化错误
            log_message(log_callback, f"执行错误: {row_excel['计划名']} - {error_msg} - 堆栈跟踪:{error_trace}", "red")

            # 日志记录完整错误
            logger.error(f"错误时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
                         f"计划名: {row_excel['计划名']}\n"
                         f"错误类型: {type(e).__name__}\n"
                         f"错误详情: {error_msg}\n"
                         f"堆栈跟踪:\n{error_trace}")

            r = Recorder(path=f'信息流-{file_path}-出错记录.csv', cache_size=500)
            plan = row_excel['计划名']
            account_id = row_excel['账号ID']
            r.add_data({'出错计划名': plan, '账号ID': account_id})
            r.record()
            continue
        finally:
            task_queue.task_done()
    log_message(log_callback, f"处理完毕", "green")  # 全部处理完毕

def start_sub_account_url(file_path, concurrency_num=2, account_id=None, log_callback=None):
    Settings.set_language("zh_cn")
    browser_path = os.path.join(os.path.dirname(__file__), 'Chrome', 'chrome.exe')
    try:
        # co = ChromiumOptions(read_file=False).set_paths(browser_path=browser_path)  # 读取浏览器方式新建配置对象
        co = ChromiumOptions(read_file=False)  # 不读取文件方式新建配置对象
        so = SessionOptions(read_file=False)
        browser = Chromium(addr_or_opts=co, session_options=so)
    except FileNotFoundError as e:
        log_message(log_callback, f"默认路径浏览器不存在，请检查是否安装谷歌浏览器,可以前往 https://www.google.cn/intl/zh-CN/chrome/ 一路下一步安装下载, ", "red")
        logger.error(f"配置文件不存在，请检查{e}")
        return

    df_sheet = get_complete_sheet1(file_path)
    missing_ids = get_set_note_ids(df_sheet, file_path=file_path) # 主表中有但是附表中不存在的笔记ID

    if missing_ids:
        log_message(log_callback, f"以下笔记ID在创意信息页面中缺失: {', '.join(missing_ids)}, 请补充必要笔记信息后再次运行", "red")
        return
    if int(concurrency_num) > 5:
        log_message(log_callback, f"并发数量暂时最大为5", "red")
        return
    # if '关联定向包' not in df_sheet.columns:
    #     log_message(log_callback, f"表格中缺少关联定向包列", "red")
    #     return

    task_queue = queue.Queue()
    # 将 Excel 数据逐行放入队列中
    for index, row in df_sheet.iterrows():
        task_queue.put(row)

    # 打开合作伙伴平台页面
    account_tab = browser.new_tab('https://partner.xiaohongshu.com/partner/subAccount-list')

    # TODO 加一个校验步骤， 不是叁柒的所属邮箱直接驳回
    account_tab.ele('tag:span@@class=d-text --color-static --color-text-paragraph --size-text-paragraph text label@@text()^登陆邮箱：').next().child().next().click(by_js=True)
    time.sleep(1)
    email_str = account_tab.ele('tag:span@@class=d-text --color-static --color-text-paragraph --size-text-paragraph text label@@text()^登陆邮箱：').next().child().text
    email_permission_list = email_list(log_callback)
    if email_str not in email_permission_list:
        log_message(log_callback, f"当前邮箱账号:【{email_str}】暂无权限使用", "red")
        return

    account_tab.ele('tag:input@@class=el-input__inner@@type=text@@autocomplete=off@@placeholder=请输入').click()
    account_tab.ele('tag:input@@class=el-input__inner@@type=text@@autocomplete=off@@placeholder=请输入').input(account_id)
    account_tab.ele('tag:button@@id=search-btn--1').click(by_js=True)

    account_tab.wait(2)

    search_none_ele = account_tab.ele('tag:tr@@class=d-table__empty-row@@text()=暂无数据', timeout=0.5)
    if search_none_ele:
        log_message(log_callback, f"合作伙伴平台子账号ID:{account_id}搜索结果为空, 检查表格第一行子账号ID, 或者检查是否登录合作伙伴平台", "red")
        return

    account_tab.ele('tag:span@@text():跳转', timeout=2).hover()

    plan_tab_arr = []  # 新建计划标签页对象数组
    threads = []
    jg_ele = account_tab.ele('tag:span@@class=d-text --color-static --color-current --size-text-paragraph d-text-nowrap d-text-ellipsis@@text():聚光平台', timeout=2)

    if jg_ele:
        create_plan_tab = jg_ele.click.for_new_tab(by_js=True)
        create_plan_tab.wait.title_change("计划列表")

        plan_url = create_plan_tab.url
        print(f"点完聚光平台以后跳转的tab:{create_plan_tab.url}")
        plan_tab_arr.append(create_plan_tab)

        if int(concurrency_num) > 1:
            for _ in range(concurrency_num - 1):
                plan_tab_arr.append(browser.new_tab(plan_url))

    for plan_tab in plan_tab_arr:
        logger.info(f"开始创建线程: {threading.current_thread().name} - 给当前线程分配的新建计划tab标签页:{id(plan_tab)}")
        t = threading.Thread(target=worker, args=(task_queue, plan_tab, file_path, log_callback, df_sheet, browser))
        t.start()
        time.sleep(2)
        threads.append(t)

    task_queue.join()

    # 发送结束信号给所有线程
    for _ in range(len(threads)):
        task_queue.put(None)

    # 等待所有线程结束
    for t in threads:
        t.join()

    # handle_all_excel_data(df_sheet, file_path, log_callback)


if __name__ == '__main__':
    file_path = r"D:\Users\Desktop\可复美a111111111.xlsx"

    browser = Chromium()  ## 临时测试、到时候删掉， 只保留调用create_unit()函数
    unit_view_tab = browser.latest_tab  ## 临时测试、到时候删掉
    unit_view_tab.get('https://ad.xiaohongshu.com/aurora/ad/create2?from=CampaignList&vSellerId=67ea0f5f2ce6e800152bd3f0')  ## 临时测试、到时候删掉

    sheet1_data = get_complete_sheet1(file_path)
    for index, row in sheet1_data.iterrows():
        if index == 0:
            create_plan(unit_view_tab, row, file_path, log_callback=None, df_sheet=sheet1_data, browser=browser)
















